package com.acceptable.qucun.file.service.impl;

import com.acceptable.qucun.file.entity.FileInfo;
import com.acceptable.qucun.file.entity.FileSource;
import com.acceptable.qucun.file.entity.TemplateDict;
import com.acceptable.qucun.file.entity.template.FileDeleteItem;
import com.acceptable.qucun.file.entity.template.FileShareItem;
import com.acceptable.qucun.file.entity.template.TemplateFile;
import com.acceptable.qucun.file.exception.FileOperationException;
import com.acceptable.qucun.file.mapper.*;
import com.acceptable.qucun.generic.entity.FileItem;
import com.acceptable.qucun.generic.entity.RedisItem;
import com.acceptable.qucun.generic.util.CodeUtil;
import com.acceptable.qucun.generic.util.FileUtil;
import com.acceptable.qucun.generic.util.RandomUtil;
import com.acceptable.qucun.generic.util.TimeUtil;
import com.acceptable.qucun.generic.util.result.error.FileErrorResult;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import javafx.beans.binding.When;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.lang.NonNull;
import org.springframework.lang.Nullable;
import org.springframework.stereotype.Service;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.*;

@Service
public class FileDatabaseOperation{
    @Autowired private TemplateFileMapper fileMapper;
    @Autowired private FileItem fileItem;
    @Autowired private RedisItem redisItem;
    @Autowired private StringRedisTemplate redisTemplate;
    @Autowired private FileMimeMapper mimeMapper;
    @Autowired private FileDeleteMapper deleteMapper;
    @Autowired private FileShareItemMapper shareItemMapper;
    @Autowired private FileTypeMapper typeMapper;
    @Autowired private FileSourceMapper fileSourceMapper;

    /**
     *  在数据库中查询指定目录下的是否存在为fileName相同名称的文件
     *  存在则依据date创建新名称
     */
    public String getNewFileName(@NonNull Integer userId,
                                 @NonNull Long parentDictId,
                                 @NonNull String fileName,
                                 @NonNull Date date){
        List<TemplateFile> fileList = fileMapper.selectSubFile(userId, parentDictId);
        for (TemplateFile file: fileList){
            if (file.getFileName().equals(fileName)){
                fileName = FileUtil.changeFileNameByDate(fileName, date);
            }
        }
        return fileName;
    }

    // 持久层数据 所有的父文件夹数据
    public List<TemplateFile> selectParentDictList(@NonNull Integer userId,
                                                   @NonNull Long fileId){
        List<TemplateFile> parentDictList = new ArrayList<>();
        TemplateFile file = fileMapper.selectCurrentParentDict(userId, fileId);
        while (file != null && file.getFileId() != 0){
            parentDictList.add(file);
            file = fileMapper.selectCurrentParentDict(userId, file.getFileId());
        }
        Collections.reverse(parentDictList);
        return parentDictList;
    }

    /**
        获取包含前缀的路径 最后不含 "/"
     */
    public String getAbsolutePath(@NonNull Integer userId,
                                  @NonNull Long fileId){
        TemplateFile file = fileMapper.selectByFileId(userId, fileId);
        if(file == null)
            return "";

        // 获取目标文件路径
        String rootPath = fileItem.getRootDictPrefix() + "/" + userId;
        StringBuilder filePath = new StringBuilder(rootPath);
        List<TemplateFile> parentDictList = this.selectParentDictList(userId, fileId);
        for (TemplateFile dict: parentDictList){
            filePath.append("/" + dict.getFileName());
        }
        filePath.append("/" + file.getFileName());
        return filePath.toString();
    }

    /**
     *  获取相对于FileItem的前缀的路径
     */
    private String getRelativePath(Integer userId,
                                   Long fileId){
        TemplateFile destDict = fileMapper.selectByFileId(userId, fileId);
        if(destDict == null)
            throw new FileOperationException(FileErrorResult.DEST_DICT_NOT_EXIST_ERROR);

        // 获取目标文件夹路径
        StringBuilder filePath = new StringBuilder();
        List<TemplateFile> parentDictList = this.selectParentDictList(userId, fileId);
        for (TemplateFile dict: parentDictList){
            filePath.append("/" + dict.getFileName());
        }
        return filePath.toString();
    }

    /**
     *  筛选未被删除的文件
     */
    public List<TemplateFile> selectUndeletedFile(@NonNull List<TemplateFile> fileList){
        List<TemplateFile> resList = new ArrayList<>();
        for (TemplateFile file: fileList){
            if (file.getDeleted())
                continue;
            resList.add(file);
        }
        return resList;
    }

    /**
     *  查询所有层级的子文件 查出后放入元素的成员变量中 (树状结构 而非简单列表)
     *  不含目录本身
     */
    public List<TemplateFile> selectSubFileTree(@NonNull Integer userId,
                                                @NonNull Long parentDictId){
        List<TemplateFile> allLevelFile = new ArrayList<>();
        for (TemplateFile file: fileMapper.selectSubFile(userId, parentDictId)){
            if (file.getDict()){
                TemplateDict dict = new TemplateDict(file);
                dict.setSubFileList(this.selectSubFileTree(userId, file.getFileId()));
                allLevelFile.add(dict);
            } else{
                allLevelFile.add(file);
            }
        }
        return allLevelFile;
    }

    /**
     *  查询所有层级的子文件
     *  与上述方法不同的是 查出所有层级的子文件后 都放在同一列表中
     *  所有文件项都处于同一级
     */
    public List<TemplateFile> selectSubFileList(@NonNull Integer userId,
                                                @NonNull Long parentDictId){
        List<TemplateFile> allSubFile = new ArrayList<>();
        for (TemplateFile file: fileMapper.selectSubFile(userId, parentDictId)){
            if (file.getDict()){
                allSubFile.addAll(this.selectSubFileList(userId, file.getFileId()));
            }
            // 不仅需要添加子文件夹下的普通文件 还须添加文件夹本身
            allSubFile.add(file);
        }
        return allSubFile;
    }

    /**
     *  移动文件的数据库操作
     *  修改一级子文件的
     *      (parentDictId, fileName[重名检测])
     *  @param operationFileList 被操作的文件列表 一级子文件列表
     *  @param date 做文件重名检测时 文件重命名需要的参数
     */
    public void moveFile(@NonNull List<TemplateFile> operationFileList,
                         @NonNull Integer userId,
                         @NonNull Long parentDictId,
                         @NonNull Date date) {
        for (TemplateFile file : operationFileList) {
            file.setUserId(userId);
            file.setPid(parentDictId);

            // 文件重名检测
            String newName = this.getNewFileName(userId, parentDictId, file.getFileName(), date);
            if (!file.getFileName().equals(newName))
                file.setFileName(newName);
            fileMapper.updateByFileId(file);
        }
    }

    /**
     *  复制文件的数据库操作
     *  修改所有层次的数据
     *      (fileId, fileName[重名检测], createTime, parentDictId, )
     *  插入所有层次的被复制文件的数据
     *  @param operationFileList 被操作的文件列表 元素中嵌套子文件夹的子文件列表
     */
    public void copyFile(@NonNull List<TemplateFile> operationFileList,
                         @NonNull Integer userId, @NonNull Long parentDictId,
                         @NonNull Date date){
        for (TemplateFile file : operationFileList) {
            file.setUserId(userId);
            file.setPid(parentDictId);

            // 文件重名检测
            String newName = this.getNewFileName(userId, parentDictId, file.getFileName(), date);
            if (!file.getFileName().equals(newName))
                file.setFileName(newName);

            // 必须先设置 让子文件设置parentDictId为该值
            Long srcFileId = file.getFileId();
            file.setFileId(new RandomUtil().fileIdUnique(13, userId, fileMapper));

            if (file.getDict() && file.getClass().getSimpleName().contains("TemplateDict")) {
                TemplateDict dict = (TemplateDict) file;
                this.copyFile(dict.getSubFileList(), userId, srcFileId, date);
            }

            LocalDateTime time = TimeUtil.date2LocalDateTime(new Date());
            file.setCreateTime(time);

            fileMapper.insert(file);
        }
    }

    /**
     *  复制/移动文件的数据库操作 反射版
     *  复制:
     *      修改所有层次的数据
     *          (fileId, fileName[重名检测], createTime, parentDictId, )
     *      插入所有层次的被复制文件的数据
     *  移动:
     *      仅修改一级子文件的数据
     *          (parentDictId, fileName[重名检测])
     *  @param operationFileList 被操作的文件列表 元素中嵌套子文件夹的子文件列表
     *  @param parentDictId 目标目录的id
     *  @param level 当前正在进行移动或者复制的层次
     *  @param isCopy 做复制(true)还是移动操作
     */
    public void copyOrMoveFile(@NonNull List<TemplateFile> operationFileList,
                               @NonNull Integer userId, @NonNull Long parentDictId,
                               @Nullable Date date, @NonNull Integer level,
                               boolean isCopy)
            throws ReflectiveOperationException {
        String method = isCopy ? "insert": "updateByFileId";
        Method mapperMethod = TemplateFileMapper.class.getDeclaredMethod(method, TemplateFile.class);
        for (TemplateFile file : operationFileList) {
            file.setUserId(userId);

            // 此处设置的是新生成的id
            file.setPid(parentDictId);

            // 文件重名检测 无论移动还是复制只有一级子文件需要进行
            // 是否有必要在数据库层面进行该操作 根据是否以物理文件名进行存储为准
            /*
                if (level == 0 && date != null){
                    String newName = this.getNewFileName(userId, parentDictId, file.getFileName(), date);
                    if (!file.getFileName().equals(newName))
                        file.setFileName(newName);
                }
             */

            if (isCopy){
                // 必须先设置 让子文件设置parentDictId为该值
                Long destFileId = new RandomUtil().fileIdUnique(13, userId, fileMapper);
                file.setFileId(destFileId);

                if (file.getDict() && file.getClass().getSimpleName().contains("TemplateDict")) {
                    TemplateDict dict = (TemplateDict) file;
                    this.copyOrMoveFile(dict.getSubFileList(), userId, destFileId,
                                        date, level + 1, isCopy);
                }

                LocalDateTime time = TimeUtil.date2LocalDateTime(new Date());
                file.setCreateTime(time);
            }

            mapperMethod.invoke(fileMapper, file);
        }
    }

    /**
     *  删除file_source中指定数据
     */
    public boolean deleteFileSource(
            @Nullable Integer userId,
            @Nullable Long fileId,
            @NonNull String fileMd5){
        QueryWrapper<FileSource> wrapper = new QueryWrapper<>();
        wrapper.eq("file_md5", fileMd5);

        if (fileId != null)
            wrapper.eq("file_id", userId);;

        if (userId != null) {
            wrapper.eq("user_id", fileId);
        }

        FileSource source = new FileSource();
        return source.delete(wrapper);
    }

    /**
     *  删除file_source中指定数据
     */
    public boolean deleteFileSource(
            @Nullable FileSource fileSource){
        QueryWrapper<FileSource> wrapper = new QueryWrapper<>();
        wrapper.eq("file_md5", fileSource.getFileMd5());

        Long fileId = fileSource.getFileId();
        if (fileId != null)
            wrapper.eq("file_id", fileId);

        Integer userId = fileSource.getUserId();
        if (userId != null) {
            wrapper.eq("user_id", userId);
        }

        FileSource source = new FileSource();
        return source.delete(wrapper);
    }

    /**
     *  通过request 传给redisInterceptor数据
     *  @param redisDataMap 已经处理好数据的需要存入redis的map
     */
    public Map<String, Object> treatedData2Redis(@NonNull String redisKey,
                                                 @NonNull HttpServletRequest request,
                                                 @NonNull Map<String, Object> redisDataMap){
        request.setAttribute("redis_key", redisKey);
        request.setAttribute("minutes", redisItem.getUploadedChunkMinutes());
        request.setAttribute("redis_map", redisDataMap);
        return redisDataMap;
    }

    /**
     *  未处理的数据在此处包装 传给redisInterceptor数据
     */
    public Map<String, Object> untreatedData2Redis(@NonNull String redisKey,
                                                   @NonNull HttpServletRequest request,
                                                   @NonNull String[] keys,
                                                   @NonNull Object[] values){
        Map<String, Object> redisMap = new HashMap<>();
        if (keys.length < values.length){
            return redisMap;
        }

        for (int i = 0; i < keys.length; i++) {
            String key = keys[i];
            Object value = i >= values.length ? null: values[i];
            redisMap.put(key, value);
        }
        this.treatedData2Redis(redisKey, request, redisMap);
        return redisMap;
    }

    /**
     *  未处理的数据
     */
    public boolean untreatedRedisStore(@NonNull String redisKey,
                                    @NonNull String[] keys,
                                    @NonNull Object[] values){
        if (keys.length != values.length)
            return false;
        Map<String, Object> redisMap = new HashMap<>();
        for (int i = 0; i < keys.length; i++){
            redisMap.put(keys[i], values[i]);
        }
        String redisValue = CodeUtil.object2String(redisMap);
        redisTemplate.opsForValue().set(redisKey, redisValue,
                Duration.ofMinutes(Long.parseLong(redisItem.getUploadedChunkMinutes())));
        return true;
    }

    /**
     *  给前端 所有的父文件夹的id 名称数据
     *  在分享状态下查看分享文件的信息时
     *  不能出现真实情况下被分享文件的父目录的路径
     *  @param shareStatus 是否是在分享状态下勘察被分享文件
     */
    public List<FileInfo> getPathDictList(@NonNull Integer userId,
                                          @NonNull Long fileId,
                                          @NonNull Boolean shareStatus){
        List<TemplateFile> parentDictList = this.selectParentDictList(userId, fileId);
        List<FileInfo> pathDictList = new ArrayList<>();

        // 若在分享状态下查询被查询文件 则应返回空集
        List<FileShareItem> itemList = shareItemMapper.selectByFileId(userId, fileId);
//        TemplateFile thisFile = fileMapper.selectByFileId(userId, fileId);
        if (shareStatus && itemList.size() > 0){
            return pathDictList;
        }

        if (shareStatus){
            for (int i = parentDictList.size() - 1; i >= 0; i--) {
                TemplateFile file = parentDictList.get(i);
                itemList = shareItemMapper.selectByFileId(userId, file.getFileId());
                FileInfo dictInfo = new FileInfo();
                dictInfo.setFileId(file.getFileId());
                dictInfo.setFileName(file.getFileName());
                pathDictList.add(dictInfo);
                // 被分享文件
                if (itemList.size() > 0){
                    break;
                }
            }

            // 由于添加是逆序添加的 所以逆置 使正序
            Collections.reverse(pathDictList);
        } else{
            for (TemplateFile file: parentDictList){
                FileInfo dictInfo = new FileInfo();
                dictInfo.setFileId(file.getFileId());
                dictInfo.setFileName(file.getFileName());
                pathDictList.add(dictInfo);
            }
        }

        return pathDictList;
    }

    /**
     *  TemplateFile 转 FileInfo
     *  文件路径列表 由调用这自行赋值
     *  如果是目录 子文件列表由调用者自行处理
     */
    public List<FileInfo> templateFile2FileInfo(@NonNull List<TemplateFile> fileList){
        List<FileInfo> fileInfoList = new ArrayList<>();
        for (TemplateFile file: fileList){
            FileInfo fileInfo = new FileInfo();
            Long fileId = file.getFileId();
            fileInfo.setFileId(fileId);
            fileInfo.setFileName(file.getFileName());
            fileInfo.setFileSize(file.getFileSize());
            fileInfo.setFileMime(file.getFileMime());

            fileInfo.setIsDict(file.getDict());
            fileInfo.setCreateTime(file.getCreateTime().toString());
            fileInfo.setLastModifyTime(file.getLastModifyTime().toString());
            fileInfoList.add(fileInfo);
        }
        return fileInfoList;
    }

    /**
     *  查询指定文件类型的文件
     */
    public List<TemplateFile> filterFileByType(@NonNull Integer userId,
                                               @NonNull String type){
        Set<String> mimeList = mimeMapper.selectMimeByType(type);
        List<TemplateFile> fileList = new ArrayList<>();
        for (String mime: mimeList){
            List<TemplateFile> tempList = fileMapper.selectFileByMime(userId, mime);
            fileList.addAll(tempList);
        }

        List<TemplateFile> existedFile = new ArrayList<>();
        for (TemplateFile file: fileList){
            if (file.getDeleted() || fileItem.getDustbinDictId().equals(file.getPid())){
                continue;
            }
            boolean flag = true;
            // 本质上dustbin目录是被删除文件的顶级父目录 理论上内循环只会执行一次
            for (TemplateFile dict: this.selectParentDictList(userId, file.getFileId())){
                if (dict.getDeleted() || fileItem.getDustbinDictId().equals(dict.getFileId())){
                    flag = false;
                    break;
                }
            }
            if (flag){
                existedFile.add(file);
            }
        }
        return existedFile;
    }

    /**
        查询处于分享状态文件
     */
    public List<TemplateFile> filterFileByShare(@NonNull Integer userId,
                                                @NonNull String share){
        return fileMapper.selectFileByShareStatus(userId);
    }

    /**
     *  查询所有处于删除状态的文件
     */
    public List<TemplateFile> filterFileByDeleted(@NonNull Integer userId){
        return fileMapper.selectAllDeletedFile(userId, fileItem.getDustbinDictId());
    }

    /**
     *  批量逻辑还原文件
     */
    public boolean batchLogicalRecovery(@NonNull Integer userId,
                                        @NonNull List<Long> fileIdList,
                                        @NonNull Date date){
        List<TemplateFile> fileList = fileMapper.selectBatchByFileId(userId, fileIdList);
        List<FileDeleteItem> deleteItemList = deleteMapper.selectBatchByFileId(userId, fileIdList);
        for (int i = 0; i < fileIdList.size(); i++){
            TemplateFile file = fileList.get(i);
            FileDeleteItem deleteItem = deleteItemList.get(i);
            file.setDeleted(false);
            file.setPid(deleteItem.getSrcPid());
            file.setLastModifyTime(TimeUtil.date2LocalDateTime(date));

        }

        int fileNum = fileMapper.updateBatchByFileId(userId, fileList);
        int itemNum = deleteMapper.deleteBatchByFileId(userId, fileIdList);
        return fileNum == itemNum && fileNum != 0;
    }
}
